package benchmark

import org.springframework.jdbc.datasource.SingleConnectionDataSource
import java.util.Properties
import com.googlecode.mapperdao.utils.{Database, Setup}
import com.googlecode.mapperdao.jdbc.Transaction
import com.googlecode.mapperdao.{ValuesMap, Entity, SurrogateIntId, UpdateConfig}
import com.googlecode.concurrent.ExecutorServiceManager

/**
 * benchmark : an attempt to isolate and benchmark mapperdao
 *
 * args: database iterations benchmark
 *
 * @author kostantinos.kougios
 *
 *         9 Oct 2012
 */
object Benchmark extends App
{
	val Loops = 10000

	benchmarkIt("postgresql", Loops, "select")
	benchmarkIt("mysql", Loops, "select")
	benchmarkIt("postgresql", Loops, "insert")
	benchmarkIt("mysql", Loops, "insert")

	def benchmarkIt(database: String, loops: Int, what: String) {
		val dataSource = singleConnectionDataSource(database)
		val (jdbc, mapperDao, queryDao, txManager) = Setup(Database.byName(database), dataSource, List(ProductEntity, AttributeEntity))

		/**
		 * History:
		 *
		 * date			loops			dt
		 * ------------------------------------
		 * 2013/03/23   10000           18645   desktop,ssd
		 * 2013/03/20	10000			16261	laptop, in a transaction
		 * 2013/02/05	1000			5116
		 * 2013/02/05	10000			32800
		 * before		1000			11500
		 * before		10000			81300
		 *
		 */
		def benchmarkInsert(loops: Int) = {
			val l = (for (i <- 1 to loops) yield p).toList
			Transaction.default(txManager) {
				() =>
					mapperDao.insertBatch(UpdateConfig.default, ProductEntity, l)
			}
		}

		def benchmarkSelect(loops: Int, inserted: List[Product with SurrogateIntId]) {
			ExecutorServiceManager.lifecycle(8, 1) {
				_ =>
					inserted.foreach {
						i =>
							mapperDao.select(ProductEntity, i.id)
					}
			}
		}

		def p = Product(
			"test product",
			Set(
				Attribute("colour", "red"),
				Attribute("colour", "green"),
				Attribute("colour", "blue")
			)
		)

		println("--------------------------------------------------------------")
		println(s"Benchmark $database with $loops iterations for ${what}")
		println("--------------------------------------------------------------")
		println("cleaning up")
		jdbc.update("delete from Product")
		jdbc.update("delete from Attribute")

		def maintain() {
			println("maintenance...")
			database match {
				case "postgresql" =>
					jdbc.update("VACUUM full")
				case "mysql" =>
					List("Product", "Attribute", "Product_Attribute").foreach {
						t =>
							jdbc.update(s"ALTER TABLE ${t} ENGINE=INNODB")
					}
			}
		}
		maintain()
		// cool off
		Thread.sleep(2000)

		println("will run for %d loops".format(loops))

		println("warm up ")
		val method = what match {
			case "insert" =>
				val m = benchmarkInsert _
				for (i <- 0 to 50) m(5)
				m
			case "select" =>
				println("inserting test data")
				val inserted = benchmarkInsert(loops)
				maintain()
				val m = benchmarkSelect(_: Int, inserted)
				m(500)
				m
		}

		println("benchmarking...")
		val start = System.currentTimeMillis
		method(loops)
		val stop = System.currentTimeMillis
		println("dt : " + (stop - start))

	}

	def singleConnectionDataSource(database: String) = {
		val properties = loadJdbcProperties(database)
		new SingleConnectionDataSource(
			properties.getProperty("url"),
			properties.getProperty("username"),
			properties.getProperty("password"), true)
	}

	def loadJdbcProperties(database: String) = {
		val properties = new Properties
		properties.load(getClass.getResourceAsStream(s"/benchmark.${database}.properties"))
		properties
	}
}

case class Product(name: String, attributes: Set[Attribute])

case class Attribute(name: String, value: String)

object ProductEntity extends Entity[Int, SurrogateIntId, Product]
{
	val id = key("id") autogenerated (_.id)
	val name = column("name") to (_.name)
	val attributes = manytomany(AttributeEntity) getter ("attributes") to (_.attributes)

	def constructor(implicit m: ValuesMap) = new Product(name, attributes) with Stored
	{
		val id: Int = ProductEntity.id
	}
}

object AttributeEntity extends Entity[Int, SurrogateIntId, Attribute]
{
	val id = key("id") autogenerated (_.id)
	val name = column("name") to (_.name)
	val value = column("value") to (_.value)
	val product = manytomanyreverse(ProductEntity) forQueryOnly() to (p => Nil)

	def constructor(implicit m: ValuesMap) = new Attribute(name, value) with Stored
	{
		val id: Int = AttributeEntity.id
	}
}
